4985 2024-07-31 2024-12-14
Spring Cache 是 Spring 框架中的一个缓存机制,它通过一系列注解(@Cacheable、@CachePut、@CacheEvict、@Caching 等)来实现对缓存的操作和管理。
最近对Spring Cache这一块有点感兴趣,稍稍花点时间从源码级别来稍微总结下(本文基于Spring Boot 2.7.8)。
一、@EnableCaching
Spring Cache的入口就是注解 @EnableCaching,这个是我们切入点。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 这里引入了前置依赖类
// 这里换成不同的导入类就可以实现不同的功能
// 比如下文的 AsyncConfigurationSelector、TransactionManagementConfigurationSelector
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
// true-基于cglib的继承机制动态代理,false-基于jdk的接口机制动态代理
boolean proxyTargetClass() default false;
// 默认为代理模式,同一子类调用代理不会生效
AdviceMode mode() default AdviceMode.PROXY;
// 动态代理的顺序
int order() default Ordered.LOWEST_PRECEDENCE;
}
1、@Import
先看下 @Import,简单理解就是一个可以批量导入未被 @ComponentScans 注解扫描到的非用户Spring组件Bean。
一般来说需要与 @Configuration 配合使用,声明该类是一个Spring系统配置类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
// 需要注入的配置类
Class<?>[] value();
}
2、@ImportSelector
再来看下 @ImportSelector、@AdviceModeImportSelector,这两个是类是 @EnableXXX 具体配置类的父类。
// 以下均为通过 @Import 注解导入,所以说这是Spring Boot新功能模块的引入标准方式
// @EnableCaching 配置类
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
}
// @EnableAsync 配置类
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
}
// @EnableTransactionManagement 配置类
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
}
// 其中 AdviceModeImportSelector implements ImportSelector
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
}
简单来说,**@ImportSelector **这个注解是用来批量导入某些系统配置类。
public interface ImportSelector {
// 根据特定注解,返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
String[] selectImports(AnnotationMetadata importingClassMetadata);
// 用户排除某些非必要类
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
@AdviceModeImportSelector 这个类是用来实现 @EnableAsync、@EnableTransactionManagement、@EnableCaching 等注解的基类,具体的子类可以根据配置参数,选择不同的配置类来实现相应功能。
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";
protected String getAdviceModeAttributeName() {
return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
}
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 省略部分代码
}
@Nullable
protected abstract String[] selectImports(AdviceMode adviceMode);
}
3、缓存配置选择器
// @Import(CachingConfigurationSelector.class)
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
// 返回特定的干活类,用来导入spring
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
// 走此处逻辑
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
// 真正干活的类是 AutoProxyRegistrar、ProxyCachingConfiguration
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
// AOP支持
result.add(AutoProxyRegistrar.class.getName());
// Cache支持
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
}
4、注入时机
通过我们前面对于Spring源码的阅读,我们应该还有印象,在BeanFactory初始化后 invokeBeanFactoryPostProcessors 方法这一环节,Spring内部框架会有一个专门的类 ConfigurationClassPostProcessor 负责扫描 @Configuration 并且触发bean初始化。
Spring Boot各模块功能bean就是在此时进行自动装配的,这里不再多做赘述。
@EnableCaching 注解讲解到这里,@EnableAsync、@EnableTransactionManagement 具体实现原理类似。接下来我们看下具体干活的类。
二、相关配置类
与Spring Cache相关类是 AutoProxyRegistrar、ProxyCachingConfiguration。其中 AutoProxyRegistrar 是配合@Configuration做动态代理的(注意@Configuration与@Component的区别),偏辅助模块。
1、AutoProxyRegistrar
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
// 简单一点总结,就是让 @Configuration 功能运行正常,重点关注引入目标类的两个属性
// proxyTargetClass-是否需要代理类、mode-代理模式,动态或静态代理
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
for (String annType : annTypes) {
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (candidate == null) {
continue;
}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
candidateFound = true;
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
}
}
接下来我们重点看下缓存相关模块类。
2、ProxyCachingConfiguration
代理配置总入口,重点看下 ProxyCachingConfiguration 类,如下
// 不为配置类下bean单独做代理,功能上等同于 @Component
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
// 简单点说就是cache增强类,下面两个bean都是服务这个bean的
// 施加增强的入口bean
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
// 配置增强器
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
// 定义的缓存的操作
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
// 拦截缓存,对缓存进行一系列设置
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource);
return interceptor;
}
}
3、AbstractCachingConfiguration
再看下 ProxyCachingConfiguration 的父类 AbstractCachingConfiguration。
父类定义了一系列用于配置 CacheManager 组件bean方法,我们继承类 CachingConfigurerSupport 类并重写相应方法就行了。
@Configuration(proxyBeanMethods = false)
public abstract class AbstractCachingConfiguration implements ImportAware {
// 读取用户自定义缓存配置
// 包括 cacheManage、cacheResolver、keyGenerator、errorHandler
@Autowired
void setConfigurers(ObjectProvider<CachingConfigurer> configurers) {
Supplier<CachingConfigurer> configurer = () -> {
List<CachingConfigurer> candidates = configurers.stream().collect(Collectors.toList());
if (CollectionUtils.isEmpty(candidates)) {
return null;
}
if (candidates.size() > 1) {
throw new IllegalStateException(candidates.size() + " implementations of " +
"CachingConfigurer were found when only 1 was expected. " +
"Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
}
return candidates.get(0);
};
useCachingConfigurer(new CachingConfigurerSupplier(configurer));
}
// 快看,我们看到了熟悉的东西
protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) {
this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager);
this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver);
this.keyGenerator = cachingConfigurerSupplier.adapt(CachingConfigurer::keyGenerator);
this.errorHandler = cachingConfigurerSupplier.adapt(CachingConfigurer::errorHandler);
}
}
4、CacheOperationSource
CacheOperationSource 类定义类Spring Cache有哪些操作注解,这些操作会被Spring正确识别并进行增强处理。
// 来自类 CacheOperationSource
public AnnotationCacheOperationSource() {
this(true);
}
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
// 注解操作代理类
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
static {
// 内置了4种需要处理的注解类
CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
}
// 本方法会判断给定目标类是否需要做增强
// 判断依据为该类的方法是含有特定注解,非类级别
@Override
public boolean isCandidateClass(Class<?> targetClass) {
return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS);
}
// 省略其他相关注解识别判断、注解属性读取设置方法
}
三、整体流程
我们先回忆一下之前AOP相关的内容
1、切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。
AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
2、增强器(Advisor)
拦截切点、施加增强,增强器可以有选择性地拦截/增强目标对象中的部分方法,可以理解为增强的集合载体。
3、增强(Advice)
增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
一般有 前置增强@Before、环绕增强 @Around、后置增强@AfterReturning/@After。
1、增强器
// 增强器bean,定义拦截规则
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
@Override
public Pointcut getPointcut() {
// 定义方法拦截点
// 主要判断方法为上文出现过的 SpringCacheAnnotationParser.isCandidateClass 方法
return this.pointcut;
}
}
// 以下3个类均来自Spring Aop模块
public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
// 省略具体方法
}
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable {
// 省略具体方法
}
public interface PointcutAdvisor extends Advisor {
// 获取切入点,一般为类的某一个方法
Pointcut getPointcut();
}
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
2、增强时机
具体施加逻辑请读者查看 Spring AOP(二),这里只简单列举下相关类
- ApplicationContext刷新后,开始初始化剩下的所有非lazy bean,此步将会触发需要缓存增强的类的bean初始化
- 到达getBean方法,目标bean将经历各种 BeanPostProcessor 的层层蹂躏
- 最终到达负责AOP相关功能的BeanPostProcessor—AbstractAutoProxyCreator,开始动态代理
- AbstractAutoProxyCreator.postProcessAfterInitialization
- AbstractAutoProxyCreator.wrapIfNecessary
最终会到达以下关键逻辑
// 来自类 AbstractAutoProxyCreator
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 这里我们寻找的bean所对应的增强器
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 根据增强器创建动态代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
// 对象已经不是你看到的对象了
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
更多AOP相关的逻辑就不做过多深入了,点到为止。
3、增强逻辑
具体的增强逻辑是由类 CacheInterceptor 来负责的。
// 注意它是一个 MethodInterceptor,定义了增强入口
// 具体干活逻辑则交给了父类 CacheAspectSupport
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
try {
// 具体缓存处理逻辑委托给父类 CacheAspectSupport
return execute(aopAllianceInvoker, target, method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
四、注解说明
1、@CacheEvict
通常用在删除方法上,用来从缓存中移除对应数据。
参数 | 说明 |
---|---|
value/cacheNames | 指定要清空的缓存名称,可多个 |
key | 指定要删除的缓存项的key,可以使用SpEL表达式 |
allEntries | 是否清空指定缓存中的所有缓存项,默认为false |
beforeInvocation | 是否在方法执行之前就清空缓存,默认为false,即在方法执行之后清空 |
condition | 清除对象的条件,非必需,需使用SpEL表达 |
keyGenerator | 用于指定key生成器,非必需 |
cacheManager | 用于指定使用哪个缓存管理器,非必需 |
cacheResolver | 用于指定使用哪个缓存解析器,非必需 |
2、@Cacheable
使其能够根据方法的请求参数对其结果进行缓存。在查询时,会先从缓存中取,若不存在才会再发起对数据库的访问。
参数 | 说明 |
---|---|
value/cacheNames | 指定缓存的名称,用于区分不同的缓存集合,可多个 |
key | 缓存对象存储在Map集合中的key值,非必需,缺省按照方法的所有参数组合作为key值 |
condition | 缓存对象的条件,非必需,需使用SpEL表达式 |
unless | 不缓存的数据,可以通过对result进行判断 |
sync | 清除对象的条件,非必需,需使用SpEL表达 |
keyGenerator | 用于指定key生成器,非必需 |
cacheManager | 用于指定使用哪个缓存管理器,非必需 |
cacheResolver | 用于指定使用哪个缓存解析器,非必需 |
3、@CachePut
能够根据方法的请求参数对其结果进行缓存。与@Cacheable不同的是,@CachePut每次都会真实调用函数,所以主要用于数据新增和修改操作上。
参数 | 说明 |
---|---|
value/cacheNames | 指定缓存的名称,用于区分不同的缓存集合,可多个 |
key | 缓存对象存储在Map集合中的key值,非必需,缺省按照方法的所有参数组合作为key值 |
condition | 缓存对象的条件,非必需,需使用SpEL表达式 |
unless | 不缓存的数据,可以通过对result进行判断 |
keyGenerator | 用于指定key生成器,非必需 |
cacheManager | 用于指定使用哪个缓存管理器,非必需 |
cacheResolver | 用于指定使用哪个缓存解析器,非必需 |
4、@Caching
组合多个Cache注解使用,以便在一个方法上同时使用@Cacheable、@CachePut、@CacheEvict等注解。
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
5、相关SpEL
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
ArgumentName | context | 当前被调用的方法的参数 | User user -> #user.id |
result | context | 方法执行后的返回值 仅当方法执行后的判断有效 如 unless cacheEvict的beforeInvocation=false | #result |
methodName | root | 当前被调用的方法名 | #root.methodname |
method | root | 当前被调用的方法 | #root.method.name |
target | root | 当前被调用的目标对象实例 | #root.target |
targetClass | root | 当前被调用的目标对象的类 | #root.targetClass |
args | root | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root | 当前被调用的方法使用的缓存列表 | #root.caches[0].name |
使用方法参数时,可以直接写成#参数名
,也可以写成:#p参数索引
,例如#p0
表示索引0的参数。
当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性
五、处理流程
1、execute
// 来自类 CacheAspectSupport
// 进入此步方法,说明target、method参数已经满足了被增强器增强的前置条件
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
// 主要是检查缓存是否存在 CacheManager
if (this.initialized) {
// 获取被代理对象的具体类
Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// 这里简单的理解,就是寻找对方法需要进行增强的具体配置,例如缓存新增、缓存过期,与注解配置一一对应
// caches=[tmp_xk_userid_blogs] | key='#userId' | keyGenerator='' | cacheManager='caffeineCacheManager' | cacheResolver='' | condition='' | unless='' | sync='false'
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
// 如果识别到满足特定条件,需要操作,则进行代理增强
// 这里需要注意下,同一个方法可能被多个注解修饰(类型可相同也可不同),所有会有多个操作
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
// 正常调用
return invoker.invoke();
}
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 同步调用,是否需要显式加锁进行同步调用,由参数 sync 控制,默认false
// 只有 @Cacheable 注解才有sync属性,且同步模式下 @Cacheable 与其他注解不兼容
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
// 这里有同步加锁的操作
return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
else {
// No caching required, just call the underlying method
return invokeOperation(invoker);
}
}
// 1.如果显式指定方法执行前清除缓存 @CacheEvict,默认是方法执行好清除缓存
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 2.再处理 @Cacheable,缓存命中取缓存,不命中则返回方法结果并将该结果放入缓存
// Check if we have a cached value matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached value is found
List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
if (cacheHit == null) {
// 收集需要进行的put操作 @CachePuts,是否满足condition条件
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
// 3.如果缓存命中
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 4.反之查询执行代理方法
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 5.收集 @CachePut,在没有命中缓存的情况下 @Cacheable = @CachePut,是否满足condition条件
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
// 6.执行真正的set操作,前面只是收集满足条件的指令
cachePutRequest.apply(cacheValue);
}
// 7.先查询数据,再进行删除,默认操作
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
2、同步处理
// 来自类 CacheAspectSupport
@Nullable
private Object handleSynchronizedGet(CacheOperationInvoker invoker, Object key, Cache cache) {
InvocationAwareResult invocationResult = new InvocationAwareResult();
Object result = cache.get(key, () -> {
invocationResult.invoked = true;
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache " + cache.getName());
}
return unwrapReturnValue(invokeOperation(invoker));
});
if (!invocationResult.invoked && logger.isTraceEnabled()) {
logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
}
return result;
}
// hashmap cache,来自类 ConcurrentMapCache
// 底层实现依赖 ConcurrentMap.computeIfAbsent,此方法保证操作线程安全
@Override
@Nullable
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) fromStoreValue(this.store.computeIfAbsent(key, k -> {
try {
return toStoreValue(valueLoader.call());
}
catch (Throwable ex) {
throw new ValueRetrievalException(key, valueLoader, ex);
}
}));
}
// spring caffeine,来自类 CaffeineCache
// 大致同 ConcurrentMap,此步会保证操作线程安全
@Nullable
public <T> T get(Object key, Callable<T> valueLoader) {
return this.fromStoreValue(this.cache.get(key, new CaffeineCache.LoadFunction(valueLoader)));
}
// spring redis,来自类 RedisCache
// 使用 synchronized 关键字进行加锁,确保单机写入串行
@Override
@SuppressWarnings("unchecked")
public <T> T get(Object key, Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
return getSynchronized(key, valueLoader);
}
@Nullable
@SuppressWarnings("unchecked")
private synchronized <T> T getSynchronized(Object key, Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value;
try {
value = valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
put(key, value);
return value;
}
3、数据过期
// 来自类 CacheAspectSupport
// 参数分别为 操作指令Context、是否在方法执行之前调用、方法执行结果
private void processCacheEvicts(
Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
for (CacheOperationContext context : contexts) {
CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
// 如果是方法之前调用,且condition匹配通过
// 这里其实有一个点,就是 beforeInvocation = true 的时候,其实啥事也不用做,因为缓存已经更新了
if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
performCacheEvict(context, operation, result);
}
}
}
private void performCacheEvict(
CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
Object key = null;
// 遍历缓存
for (Cache cache : context.getCaches()) {
// 相关代码 CacheEvictOperation.Builder.setCacheWide(cacheEvict.allEntries())
// 如果是清除所有缓存
if (operation.isCacheWide()) {
// 打印日志
logInvalidating(context, operation, null);
// 清除数据,默认第二个参数为true,即立即删除-invalidate,而非延时删除-clear
doClear(cache, operation.isBeforeInvocation());
}
else {
// 清除特定缓存
if (key == null) {
// 通过spel生成key,剩下操作大致同上
key = generateKey(context, result);
}
logInvalidating(context, operation, key);
doEvict(cache, key, operation.isBeforeInvocation());
}
}
}
4、数据查找
// 来自类 CacheAspectSupport
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
// 未命中则返回null
return null;
}
@Nullable
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
for (Cache cache : context.getCaches()) {
Cache.ValueWrapper wrapper = doGet(cache, key);
// 任意一个缓存命中则直接返回
if (wrapper != null) {
if (logger.isTraceEnabled()) {
logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
}
return wrapper;
}
}
return null;
}
5、数据新增
// 来自类 CacheAspectSupport.CachePutRequest
public void apply(@Nullable Object result) {
if (this.context.canPutToCache(result)) {
// 每个缓存都会进行存储
for (Cache cache : this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
// unless 字段专门用于对结果处理,是否缓存本次方法执行结果
// 因为前面已经处理过condition了,这里只需要考虑unless就行了
protected boolean canPutToCache(@Nullable Object value) {
String unless = "";
if (this.metadata.operation instanceof CacheableOperation) {
unless = ((CacheableOperation) this.metadata.operation).getUnless();
}
else if (this.metadata.operation instanceof CachePutOperation) {
unless = ((CachePutOperation) this.metadata.operation).getUnless();
}
if (StringUtils.hasText(unless)) {
EvaluationContext evaluationContext = createEvaluationContext(value);
return !evaluator.unless(unless, this.metadata.methodKey, evaluationContext);
}
return true;
}
六、测试代码
场景较多,读者可以自行测试,这里不做过多展开了。
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
/**
* vm参数启用assert:-ea
*/
public class CacheTest {
public static void main(String[] args) {
// 获取logback的Logger对象,root是根级别
Logger logger = (Logger) LoggerFactory.getLogger("org.springframework.cache");
// 设置日志级别
logger.setLevel(Level.TRACE);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CacheTest.class, CacheConfig.class);
CacheTest bean = context.getBean(CacheTest.class);
// 缓存初步使用
String r1 = bean.testGet1(1);
System.out.println(r1);
assert r1.equals(bean.testGet1(1));
assert r1.equals(bean.testGet1(1));
// 不缓存空数据
String r2 = bean.testGet2(2);
assert StrUtil.isNotEmpty(r2);
assert StrUtil.isEmpty(bean.testGet2(3));
assert r2.equals(bean.testGet2(2));
System.out.println("所有测试通过");
// 测试数据清除
String r3 = bean.testGet3(2);
assert !r2.equals(r3);
r2 = bean.testGet2(2);
assert r2.equals(bean.testGet2(2));
}
@EnableCaching
static class CacheConfig extends CachingConfigurerSupport {
// 可以配置多个,也可以自定义逻辑
@Bean
@Override
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("no_name");
}
}
@Cacheable(cacheNames = "no_name", key = "'user1_' + #userId")
public String testGet1(Integer userId) {
return "userId=" + userId + "_" + RandomUtil.randomString(4);
}
@Cacheable(cacheNames = "no_name", key = "'user2_' + #userId", unless = "#result == null || #result == ''")
public String testGet2(Integer userId) {
return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
}
@CacheEvict(cacheNames = "no_name", key = "'user2_' + #userId")
public String testGet3(Integer userId) {
return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
}
@CacheEvict(cacheNames = "no_name", key = "'user2' + #userId", condition = "#p0 % 3 == 0")
public String testGet4(Integer userId) {
return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
}
}
打完收工,又是一种豁然开朗的感觉,共勉!
总访问次数: 11次, 一般般帅 创建于 2024-07-31, 最后更新于 2024-12-14
欢迎关注微信公众号,第一时间掌握最新动态!